﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Diagnostics;
using System.Text;

namespace ProjectEuler.Linq
{
    internal static class Enumerable
    {
        public static IEnumerable<T[]> AllSubsequences<T>(this IEnumerable<T> sequence, Int32 size)
        {
            var result = new List<T>(size);

            foreach (var item in sequence)
            {
                result.Add(item);

                if (result.Count == size)
                {
                    yield return result.ToArray();
                    result.RemoveAt(0);
                }
            }

            if (result.Count == size)
            {
                yield return result.ToArray();
            }
        }

        public static IEnumerable<T> Except<T>(this IEnumerable<T> source, T except)
        {
            return source.Except(except, null);
        }

        public static IEnumerable<T> Except<T>(this IEnumerable<T> source, T except, IEqualityComparer<T> comparer)
        {
            if (comparer == null)
            {
                comparer = EqualityComparer<T>.Default;
            }

            return source.Where(x => !comparer.Equals(x, except));
        }

        public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
        {
            foreach (var item in source)
            {
                action(item);
            }
        }

        public static Int32 IndexOfFirst<T>(this IEnumerable<T> source, Func<T, Boolean> predicate)
        {
            var index = 0;

            foreach (var item in source)
            {
                if (predicate(item))
                {
                    return index;
                }

                index++;
            }

            return -1;
        }

        public static IEnumerable<T[]> Permutations<T>(this IEnumerable<T> source)
        {
            var src = source.ToArray();

            if (src.Length < 1)
            {
                throw new ArgumentException();
            }

            var count = (src.Length).Factorial();

            for (int i = 0; i < count; i++)
            {
                yield return Permutation(source, i);
            }
        }

        public static T[] Permutation<T>(this IEnumerable<T> source, Int32 number)
        {
            var result = source.ToArray();
            var factorial = (result.Length - 1).Factorial();

            if (number == 0)
            {
                return result;
            }

            if ((number < 0) || (number / result.Length >= factorial))
            {
                throw new ArgumentOutOfRangeException("number");
            }

            for (int i = 0; i < result.Length - 1; i++)
            {
                var tempi = (Int32)(number / factorial) % (result.Length - i);
                var temp = result[i + tempi];

                for (int j = i + tempi; j > i; j--)
                {
                    result[j] = result[j - 1];
                }

                result[i] = temp;
                factorial /= (result.Length - (i + 1));
            }

            return result;
        }

        public static Rational Product(this IEnumerable<Rational> source)
        {
            var n = 1;
            var d = 1;

            foreach (var r in source)
            {
                n *= r.Numerator;
                d *= r.Denominator;
            }

            return new Rational(n, d);
        }

        public static IEnumerable<Combination<T>> Combinations<T>(this IEnumerable<T> source, Int32 k)
        {
            var c = new Combination<T>(source, k);

            while (c != null)
            {
                yield return c;
                c = c.Next();
            }
        }

        public static Combination<T> Combination<T>(this IEnumerable<T> source, Int32 k, Int32 m)
        {
            return new Combination<T>(source, k).Element(m);
        }

        public static Int32 NumberValue(this IEnumerable<Char> source)
        {
            return (from ch in source
                    let c = Char.ToUpperInvariant(ch)
                    select 1 + (c - 'A')).Sum();
        }
        
        public static String ToJoinedString<T>(this IEnumerable<T> source)
        {
            return ToJoinedString(source, String.Empty);
        }

        public static String ToJoinedString<T>(this IEnumerable<T> source, String separator)
        {
            var result = new StringBuilder();

            foreach (var item in source)
            {
                result.Append(item);
                result.Append(separator);
            }

            if (result.Length >= separator.Length)
            {
                result.Length -= separator.Length;
            }

            return result.ToString();
        }

        public static T WithMax<T, TKey>(this IEnumerable<T> source, Func<T, TKey> keySelector)
        {
            return WithMax(source, keySelector, null);
        }

        public static T WithMax<T, TKey>(this IEnumerable<T> source, Func<T, TKey> keySelector, IComparer<TKey> keyComparer)
        {
            if (keyComparer == null)
            {
                keyComparer = Comparer<TKey>.Default;
            }

            var max = default(T);
            var maxKey = default(TKey);

            foreach (var item in source)
            {
                var key = keySelector(item);

                if (keyComparer.Compare(key, maxKey) > 0)
                {
                    max = item;
                    maxKey = key;
                }
            }

            return max;
        }
    }
}